import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { ChangeDetectionStrategy, Component, ViewChild } from '@angular/core';
import { TreeSelectComponent } from './treeselect.component';
import { TreeSelectModule } from './treeselect.module';
import { FormsModule } from '@angular/forms';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';

@Component({
  template: `
    <lv-tree-select
      [lvTreeData]="data"
      [(ngModel)]="selection"
      (ngModelChange)="onChange($event)"
      [lvSize]="size"
      [lvLoading]="loading"
      [lvDisabled]="disabled"
      [lvPlaceholder]="placeHolder"
      [lvTreeSelectionMode]="treeSelectionMode"
      [lvTreeSelectionAssociate]="treeSelectionAssociate"
      [lvShowClear]="showClear"
      [lvTreeShowCheckbox]="showCheckbox"
      [lvKeepExpanded]="keepExpanded"
      [lvShowFilter]="showFilter"
      [lvFilterKey]="filterKey"
      [lvFilterMode]="filterMode"
      [lvBeforeOpen]="beforeOpen"
      [lvPanelClass]="panelClass"
      [lvPanelWidth]="panelWidth"
      (lvClearChange)="clearChange()"
      (lvOpenChange)="openChange()"
      (lvFilterChange)="filterChange()"
    ></lv-tree-select>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
class TestTreeSelectComponent {
  @ViewChild(TreeSelectComponent, { static: false }) treeSelect: TreeSelectComponent;

  data = [
    {
      label: 'root node 1',
      expanded: true,
      children: [
        {
          label: 'child node 1-1 long long long long long long long long long long long long name.doc',
          expanded: true,
          contentToggleIcon: 'icon-storage',
          children: [
            { label: 'Leaf node 1-1-1', contentToggleIcon: 'icon-storage', isLeaf: true },
            { label: 'Leaf node 1-1-2', contentToggleIcon: 'icon-storage', isLeaf: true },
          ],
        },
        {
          label: 'child node 1-2',
          children: [
            { label: 'Leaf node 1-2-1', isLeaf: true },
            { label: 'Leaf node 1-2-2', showCheckbox: false, contentToggleIcon: '', isLeaf: true },
          ],
        },
      ],
    },
    {
      label: 'root node 2',
      children: [
        {
          label: 'Leaf node 2-1',
          isLeaf: true,
        },
        {
          label: 'child node 2-2',
          disabled: true,
          children: [
            { label: 'Leaf node 2-2-1', isLeaf: true },
            { label: 'Leaf node 2-2-2', isLeaf: true },
          ],
        },
      ],
    },
  ];
  selection = [];
  size = 'default';
  loading = false;
  disabled = false;
  treeSelectionMode = 'multiple';
  treeSelectionAssociate = true;
  showClear = false;
  keepExpanded = true;
  filterKey;
  showFilter = false;
  filterMode = 'contains';
  showCheckbox = false;
  placeHolder;
  beforeOpen;
  panelClass;
  panelWidth;
  clearChange() {}
  openChange() {}
  onChange() {}
  filterChange() {}
  collapseAll() {
    this.toggle(this.data, false);
    this.data = [...this.data];
  }

  toggle(nodes, status) {
    nodes.forEach(item => {
      item.expanded = status;
      if (item.children) {
        this.toggle(item.children, status);
      }
    });
  }
}

describe('TreeSelectComponent', () => {
  beforeEach(
    waitForAsync(() => {
      TestBed.configureTestingModule({
        declarations: [TestTreeSelectComponent],
        imports: [TreeSelectModule, FormsModule, NoopAnimationsModule],
        teardown: { destroyAfterEach: false },
      }).compileComponents();
    })
  );
  describe('render', () => {
    let component: TestTreeSelectComponent;
    let fixture: ComponentFixture<TestTreeSelectComponent>;
    const renderComp = fakeAsync(para => {
      for (const [key, value] of Object.entries(para)) {
        component[key] = value;
      }
      fixture.detectChanges();
      tick(1500);
      fixture.detectChanges();
      expect(fixture).toMatchSnapshot();
      tick(1500);
    });

    beforeEach(() => {
      fixture = TestBed.createComponent(TestTreeSelectComponent);
      component = fixture.componentInstance;
    });
    // 选择框大小渲染
    it('with lvSize', () => {
      renderComp({ size: 'large' });
    });

    // 默认文字渲染
    it('with lvPlaceholder', () => {
      renderComp({ placeHolder: 'test' });
    });

    // loading状态渲染
    it('with loading', () => {
      renderComp({ loading: true });
    });

    // disabled渲染
    it('with disabled', () => {
      renderComp({ disabled: true });
    });
  });

  describe('work', () => {
    let component: TestTreeSelectComponent;
    let fixture: ComponentFixture<TestTreeSelectComponent>;

    // 点击弹出选择面板
    const clickPanel = () => {
      const select = fixture.nativeElement.querySelector('lv-tree-select');
      select.dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();
      tick(1500);
    };

    const getContent = () => document.querySelectorAll('.lv-treenode-content');

    beforeEach(fakeAsync(() => {
      fixture = TestBed.createComponent(TestTreeSelectComponent);
      component = fixture.componentInstance;
      fixture.detectChanges();
      tick(500);
      // 单测中会调用treeSelect的更新overlayPosition报错，因此mock该方法不调用，交互测试无影响
      component.treeSelect.updateOverlayPosition = jest.fn().mockImplementation(() => {});
    }));

    // 单选生效（lvSelection默认选中项生效）（多次点击同一个不会反选），lvShowClear清除按钮点击清除，lvClearChange回调触发
    it('correctly with lvSelectionMode pass single', fakeAsync(() => {
      jest.spyOn(component, 'onChange');
      jest.spyOn(component, 'clearChange');
      component.selection = [component.data[0].children[0].children[1]];
      component.treeSelectionMode = 'single';
      component.showClear = true;
      tick(1500);
      fixture.detectChanges();
      tick(1500);
      fixture.detectChanges();
      // 默认选中值生效
      const selectValue = fixture.nativeElement.querySelector('.lv-select-value');
      expect(selectValue.textContent).toEqual('Leaf node 1-1-2');

      // 多次选择同一个选项，不会反选
      for (let i = 0; i < 2; i++) {
        clickPanel();
        const content = getContent()[2];
        content.dispatchEvent(new Event('click'));
        tick(1500);
        fixture.detectChanges();
        tick(1500);
        fixture.detectChanges();
        // 点击选择后，选中值生效，ngModelChange返回值生效
        expect(selectValue.textContent).toEqual('Leaf node 1-1-1');
        expect(component.onChange).toBeCalledWith([component.data[0].children[0].children[0]]);
      }

      // 清除按钮点击清除
      const clearIcon = fixture.nativeElement.querySelector('.lv-select-clear .lv-icon-host');
      clearIcon.dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();
      expect(selectValue.textContent).toEqual('');
      expect(component.clearChange).toBeCalled();
    }));

    // 多选生效（lvSelection默认选中项生效），ngModelChange回调，lvOpenChange面板下拉回调触发
    it('correctly with lvSelectionMode pass multiple', fakeAsync(() => {
      jest.spyOn(component, 'onChange');
      jest.spyOn(component, 'openChange');
      component.selection = [component.data[0].children[0].children[0], component.data[0].children[0].children[1]];
      tick(1500);
      fixture.detectChanges();
      tick(1500);
      fixture.detectChanges();
      // 默认选中值生效
      const selectValue = fixture.nativeElement.querySelector('.lv-select-value');
      expect(selectValue.textContent).toContain('Leaf node 1-1-1');
      expect(selectValue.textContent).toContain('Leaf node 1-1-2');

      clickPanel();
      expect(component.openChange).toBeCalled();
      const content = getContent();
      content[0].dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();
      tick(1500);
      fixture.detectChanges();
      expect(fixture.nativeElement.querySelectorAll('.lv-select-trigger-default > .lv-trigger-item').length).toBe(7);
      expect(component.onChange).toBeCalled();
    }));

    // lvTreeSelectionAssociate传入为false时，父子节点非关联选中
    test('correctly with lvTreeSelectionAssociate', fakeAsync(() => {
      component.treeSelectionAssociate = false;
      fixture.detectChanges();
      clickPanel();
      const content = getContent();
      content[0].dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();
      tick(1500);
      fixture.detectChanges();
      // 选中父节点，但不会选中子节点
      expect(fixture.nativeElement.querySelectorAll('.lv-select-trigger-default > .lv-trigger-item').length).toBe(1);
    }));

    // 不同lvFilterKey搜索的主键，lvFilterMode搜索模式
    it('correctly with lvFilterKey and lvFilterMode', fakeAsync(() => {
      jest.spyOn(component, 'filterChange');
      component.showFilter = true;
      component.filterKey = 'label';
      component.filterMode = 'startsWith';
      fixture.detectChanges();

      clickPanel();
      const input = document.querySelector('.lv-input') as HTMLInputElement;
      input.value = 'Leaf node 1-1';
      input.dispatchEvent(new Event('input'));
      tick(1500);
      fixture.detectChanges();
      tick(1500);
      expect(getContent().length).toBe(4);
      expect(component.filterChange).toBeCalled();
    }));

    // lvKeepExpanded过滤条件清除之后保留展开能力生效
    test('correctly with lvKeepExpanded', fakeAsync(() => {
      component.showFilter = true;
      component.filterKey = 'label';
      fixture.detectChanges();

      clickPanel();
      // 先搜索
      const input = document.querySelector('.lv-input') as HTMLInputElement;
      input.value = 'test';
      input.dispatchEvent(new Event('input'));
      tick(1500);
      fixture.detectChanges();

      // 清除搜索，全部展开
      const searchClear = document.querySelector('.lv-search-clear');
      searchClear.dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();
      tick(1500);
      expect(getContent().length).toBe(12);

      // 更改keepExpanded为false，收起全部
      component.keepExpanded = false;
      component.collapseAll();
      tick(1500);
      fixture.detectChanges();
      tick(1500);
      fixture.detectChanges();
      tick(1500);
      expect(getContent().length).toBe(2);
    }));

    // 搜索后关闭，再次打开原搜索信息还在
    it('correctly when search reopen', fakeAsync(() => {
      jest.spyOn(component, 'openChange');
      component.showFilter = true;
      component.filterKey = 'label';
      fixture.detectChanges();

      clickPanel();
      expect(component.openChange).toBeCalledTimes(1);
      const input = document.querySelector('.lv-input') as HTMLInputElement;
      input.value = 'test';
      input.dispatchEvent(new Event('input'));
      tick(1500);
      fixture.detectChanges();

      // 关闭搜索
      clickPanel();
      expect(component.openChange).toBeCalledTimes(2);
      tick(1500);
      fixture.detectChanges();
      tick(1500);
      fixture.detectChanges();

      // 重新打开会触发treeSearch调用再次搜索，但是这时候lvTree还没渲染出来，会报错，因此mock该方法只判断treeSearch调用
      jest.spyOn(component.treeSelect, 'treeSearch');
      component.treeSelect.treeSearch = jest.fn().mockImplementation(() => {});
      clickPanel();
      expect(component.treeSelect.treeSearch).toBeCalled();
    }));

    // 自定义下拉面板样式传入生效
    test('correctly with lvPanelClass and lvPanelWidth', fakeAsync(() => {
      component.panelClass = 'testClass';
      component.panelWidth = '30px';
      fixture.detectChanges();

      clickPanel();
      expect(document.querySelector('.testClass')).not.toBeNull();
      expect(document.querySelector('.cdk-overlay-pane').getAttribute('style')).toContain('width: 30px');
    }));

    // lvBeforeOpen打开下拉面板前回调触发，传入false阻止打开
    it('correctly with lvBeforeOpen', fakeAsync(() => {
      component.beforeOpen = () => false;
      fixture.detectChanges();

      clickPanel();
      expect(getContent().length).toBe(0);
    }));

    // 选择父节点后，再次删除父节点，查看子节点未被选中
    it('correctly when select parent node', fakeAsync(() => {
      component.showCheckbox = true;
      fixture.detectChanges();

      clickPanel();
      const content = getContent();
      content[1].dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();
      tick(1500);
      fixture.detectChanges();
      // 子节点选中态
      expect(document.querySelectorAll('.lv-checkbase-checked').length).toBe(3);

      // 删除父节点标签
      const deleteIcon = fixture.nativeElement.querySelector('.lv-icon-host');
      deleteIcon.dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();
      tick(1500);
      fixture.detectChanges();

      // 子节点不再选中
      expect(document.querySelectorAll('.lv-checkbase-checked').length).toBe(0);
    }));
  });
});
